﻿#region Copyright (c) 02/22/2012, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using Amarok.Presentation.Resources;


namespace Amarok.Presentation
{
	/// <summary>
	/// This type represents a dynamic data collection that provides notifications when items get added, removed, 
	/// or when the whole collection is refreshed. This collection type is an alternative to 
	/// <see cref="ObservableCollection{T}"/> providing additional methods for manipulating the collection and its 
	/// contained items.
	/// </summary>
	public class ViewModelCollection<T> : Collection<T>,
		INotifyPropertyChanged,
		INotifyCollectionChanged
	{
		// constants
		private const String __propertyNameCount = "Count";
		private const String __propertyNameIndexer = "Item[]";
		private const String __propertyNameHasItems = "HasItems";

		// data
		private readonly Lazy<HashSet<String>> mLazyPropertyNames;
		private readonly List<T> mList;
		private PropertyChangedEventHandler mPropertyChangedEvent;
		private NotifyCollectionChangedEventHandler mCollectionChangedEvent;

		// state
		private Int32 mBusyCount;
		private Boolean mChangedEventsSuspended;


		#region ++ INotifyPropertyChanged Interface ++

		/// <summary>
		/// This event is raised when a property value changed. The property name in the event arguments indicates
		/// which property has changed. An empty string or null for the property name indicates that all of the 
		/// properties on this object have changed.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged
		{
			add
			{
				mPropertyChangedEvent += value;
			}
			remove
			{
				mPropertyChangedEvent -= value;
			}
		}

		#endregion

		#region ++ INotifyCollectionChanged Interface ++

		/// <summary>
		/// This event is raised when a change to the collection occurs to notify listeners of dynamic changes, 
		/// such as when items get added and removed or the whole collection is refreshed.
		/// </summary>
		public event NotifyCollectionChangedEventHandler CollectionChanged
		{
			add
			{
				mCollectionChangedEvent += value;
			}
			remove
			{
				mCollectionChangedEvent -= value;
			}
		}

		#endregion

		#region ++ Public Interface (BeginUpdate, EndUpdate) ++

		/// <summary>
		/// Suspends notifications about property changes. Neither registered event handler on 
		/// <see cref="PropertyChanged"/> nor registered custom property changed handler will be called.
		/// </summary>
		public void BeginUpdate()
		{
			mChangedEventsSuspended = true;
		}

		/// <summary>
		/// Resumes notifications about property changes.
		/// </summary>
		public void EndUpdate()
		{
			mChangedEventsSuspended = false;
		}

		#endregion

		#region ++ Public Interface (RaisePropertyChanged) ++

		/// <summary>
		/// Raises a property changed event that notifies that all of the properties on this object have changed. 
		/// This method can be called once after setting various view model properties to bulk-update the 
		/// data-bound view.
		/// </summary>
		public void RaisePropertiesChanged()
		{
			_RaisePropertyChanged(null);
		}

		/// <summary>
		/// Raises the property changed event to notify that a property on this object has changed.
		/// </summary>
		/// 
		/// <param name="propertyExpression">
		/// A lambda expression specifying the property that has changed.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// The lambda expression is not a member access expression.</exception>
		/// <exception cref="ArgumentException">
		/// The lambda expression does not refer to a property.</exception>
		/// <exception cref="ArgumentException">
		/// The lambda expression does not refer to an instance member.</exception>
		public void RaisePropertyChanged<R>(Expression<Func<R>> propertyExpression)
		{
			Verify.NotNull(propertyExpression, "propertyExpression");

			var propertyName = Helper.GetPropertyName(
				propertyExpression);

			_RaisePropertyChanged(propertyName);
		}

		/// <summary>
		/// Raises the property changed event to notify that a property on this object has changed.
		/// </summary>
		/// 
		/// <param name="propertyName">
		/// The name of the property that has changed. Optional, if left empty the name of the calling method,
		/// property or event is inserted. The property name is verified only in DEBUG and TEST builds.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// An empty string was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// The type does not contain a public instance member property with specified name.</exception>
		public void RaisePropertyChanged(
			[CallerMemberName] String propertyName = null)
		{
			VerifyPropertyName(propertyName);
			_RaisePropertyChanged(propertyName);
		}

		#endregion

		#region ++ Public Interface (RaiseCollectionChanged) ++

		/// <summary>
		/// Raises a property and collection changed event that notifies that all of the properties on this object 
		/// have changed. This method can be called once after setting various view model properties to bulk-update 
		/// the data-bound view.
		/// </summary>
		public void RaiseCollectionChanged()
		{
			_RaisePropertyChanged(__propertyNameCount);
			_RaisePropertyChanged(__propertyNameIndexer);
			_RaisePropertyChanged(__propertyNameHasItems);
			_RaiseCollectionChangedReset();
		}

		#endregion

		#region ++ Public Interface (Properties) ++

		/// <summary>
		/// Gets a boolean value indicating whether the collection has items.
		/// </summary>
		public Boolean HasItems
		{
			get
			{
				return this.Count > 0;
			}
		}

		#endregion

		#region ++ Public Interface (Construction) ++

		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		public ViewModelCollection()
		{
			mLazyPropertyNames = new Lazy<HashSet<String>>(
				_ResolvePropertyNames,
				false);

			// HACK: We take the base classes items property and cast it to its concrete implementation type, 
			//       because we want to use the implementation type's methods like AddRange, InsertRange, etc. 
			//       for better performance.
			//
			mList = base.Items as List<T>;
			Debug.Assert(mList != null, "Collection<T>.Items is not implemented by List<T>!");
		}

		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		/// 
		/// <param name="collection">
		/// The collection from which the elements are copied.</param>
		/// 
		/// <remarks>
		/// The elements are copied in the same order they are read by the enumerator of the collection.</remarks>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public ViewModelCollection(IEnumerable<T> collection)
		{
			Verify.NotNull(collection, "collection");

			mLazyPropertyNames = new Lazy<HashSet<String>>(
				_ResolvePropertyNames,
				false);

			// HACK: We take the base classes items property and cast it to its concrete implementation type, 
			//       because we want to use the implementation type's methods like AddRange, InsertRange, etc. 
			//       for better performance.
			//
			mList = base.Items as List<T>;
			Debug.Assert(mList != null, "Collection<T>.Items is not implemented by List<T>!");

			// copy items
			mList.AddRange(collection);
		}

		#endregion

		#region ++ Public Interface (AddRange) ++

		/// <summary>
		/// Adds the elements of the specified collection to the end of this collection.
		/// </summary>
		/// 
		/// <param name="collection">
		/// The collection whose elements should be added to the end of this collection. The collection itself 
		/// cannot be null, but it can contain elements that are null, if type T is a reference type.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="InvalidOperationException">
		/// Reentrant attempts to change this collection are disallowed.</exception>
		public void AddRange(IEnumerable<T> collection)
		{
			VerifyReentrancy();

			// store starting index
			var startIndex = base.Count;

			// copy items
			mList.AddRange(collection);

			// return, if no items were copied
			if (base.Count == startIndex)
				return;

			// raise change events
			_RaisePropertyChanged(__propertyNameCount);
			_RaisePropertyChanged(__propertyNameIndexer);
			_RaisePropertyChanged(__propertyNameHasItems);

			_RaiseCollectionChangedAdded(() =>
				{
					var list = collection as IList;
					return list ?? collection.ToArray();
				},
				startIndex);
		}

		#endregion

		#region ++ Public Interface (Exists) ++

		/// <summary>
		/// Determines whether the collection contains elements that match the conditions defined by the 
		/// specified predicate.
		/// </summary>
		/// 
		/// <param name="match">
		/// The predicate delegate that defines the conditions of the elements to search for.</param>
		/// 
		/// <returns>
		/// true if the collection contains one or more elements that match the conditions defined by the 
		/// specified predicate; otherwise, false.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Boolean Exists(Predicate<T> match)
		{
			return mList.Exists(match);
		}

		#endregion

		#region ++ Public Interface (Find, FindLast, FindAll) ++

		/// <summary>
		/// Searches for an element that matches the conditions defined by the specified predicate, and 
		/// returns the first occurrence within the entire collection.
		/// </summary>
		/// 
		/// <param name="match">
		/// The predicate delegate that defines the conditions of the elements to search for.</param>
		/// 
		/// <returns>
		/// The first element that matches the conditions defined by the specified predicate, if found; 
		/// otherwise, the default value for type T.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public T Find(Predicate<T> match)
		{
			return mList.Find(match);
		}

		/// <summary>
		/// Searches for an element that matches the conditions defined by the specified predicate, and 
		/// returns the last occurrence within the entire collection.
		/// </summary>
		/// 
		/// <param name="match">
		/// The predicate delegate that defines the conditions of the elements to search for.</param>
		/// 
		/// <returns>
		/// The last element that matches the conditions defined by the specified predicate, if found; 
		/// otherwise, the default value for type T.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public T FindLast(Predicate<T> match)
		{
			return mList.FindLast(match);
		}

		/// <summary>
		/// Retrieves all the elements that match the conditions defined by the specified predicate.
		/// </summary>
		/// 
		/// <param name="match">
		/// The predicate delegate that defines the conditions of the elements to search for.</param>
		/// 
		/// <returns>
		/// A list containing all the elements that match the conditions defined by the specified predicate, 
		/// if found; otherwise, an empty list.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public List<T> FindAll(Predicate<T> match)
		{
			return mList.FindAll(match);
		}

		#endregion

		#region ++ Public Interface (FindIndex, FindLastIndex) ++

		/// <summary>
		/// Searches for an element that matches the conditions defined by the specified predicate, and returns 
		/// the zero-based index of the first occurrence within the entire collection.
		/// </summary>
		/// 
		/// <param name="match">
		/// The predicate delegate that defines the conditions of the element to search for.</param>
		/// 
		/// <returns>
		/// The zero-based index of the first occurrence of an element that matches the conditions defined 
		/// by match, if found; otherwise, -1.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Int32 FindIndex(Predicate<T> match)
		{
			return mList.FindIndex(match);
		}

		/// <summary>
		/// Searches for an element that matches the conditions defined by the specified predicate, and returns 
		/// the zero-based index of the first occurrence within the range of elements in the collection that 
		/// extends from the specified index to the last element.
		/// </summary>
		/// 
		/// <param name="startIndex">
		/// The zero-based starting index of the search.</param>
		/// <param name="match">
		/// The predicate delegate that defines the conditions of the element to search for.</param>
		/// 
		/// <returns>
		/// The zero-based index of the first occurrence of an element that matches the conditions defined 
		/// by match, if found; otherwise, -1.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument startIndex is outside the range of valid indexes for the collection.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Int32 FindIndex(Int32 startIndex, Predicate<T> match)
		{
			return mList.FindIndex(startIndex, match);
		}

		/// <summary>
		/// Searches for an element that matches the conditions defined by the specified predicate, and returns 
		/// the zero-based index of the first occurrence within the range of elements in the collection that 
		/// starts at the specified index and contains the specified number of elements.
		/// </summary>
		/// 
		/// <param name="startIndex">
		/// The zero-based starting index of the search.</param>
		/// <param name="count">
		/// The number of elements in the section to search.</param>
		/// <param name="match">
		/// The predicate delegate that defines the conditions of the element to search for.</param>
		/// 
		/// <returns>
		/// The zero-based index of the first occurrence of an element that matches the conditions defined 
		/// by match, if found; otherwise, -1.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument startIndex is outside the range of valid indexes for the collection.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument count is less than 0.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The arguments startIndex and count do not specify a valid section in the collection.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Int32 FindIndex(Int32 startIndex, Int32 count, Predicate<T> match)
		{
			return mList.FindIndex(startIndex, count, match);
		}


		/// <summary>
		/// Searches for an element that matches the conditions defined by the specified predicate, and returns 
		/// the zero-based index of the last occurrence within the entire collection.
		/// </summary>
		/// 
		/// <param name="match">
		/// The predicate delegate that defines the conditions of the element to search for.</param>
		/// 
		/// <returns>
		/// The zero-based index of the last occurrence of an element that matches the conditions defined 
		/// by match, if found; otherwise, -1.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Int32 FindLastIndex(Predicate<T> match)
		{
			return mList.FindLastIndex(match);
		}

		/// <summary>
		/// Searches for an element that matches the conditions defined by the specified predicate, and returns 
		/// the zero-based index of the last occurrence within the range of elements in the collection that 
		/// extends from the specified index to the last element.
		/// </summary>
		/// 
		/// <param name="startIndex">
		/// The zero-based starting index of the search.</param>
		/// <param name="match">
		/// The predicate delegate that defines the conditions of the element to search for.</param>
		/// 
		/// <returns>
		/// The zero-based index of the last occurrence of an element that matches the conditions defined 
		/// by match, if found; otherwise, -1.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument startIndex is outside the range of valid indexes for the collection.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Int32 FindLastIndex(Int32 startIndex, Predicate<T> match)
		{
			return mList.FindLastIndex(startIndex, match);
		}

		/// <summary>
		/// Searches for an element that matches the conditions defined by the specified predicate, and returns 
		/// the zero-based index of the last occurrence within the range of elements in the collection that 
		/// starts at the specified index and contains the specified number of elements.
		/// </summary>
		/// 
		/// <param name="startIndex">
		/// The zero-based starting index of the search.</param>
		/// <param name="count">
		/// The number of elements in the section to search.</param>
		/// <param name="match">
		/// The predicate delegate that defines the conditions of the element to search for.</param>
		/// 
		/// <returns>
		/// The zero-based index of the last occurrence of an element that matches the conditions defined 
		/// by match, if found; otherwise, -1.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument startIndex is outside the range of valid indexes for the collection.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument count is less than 0.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The arguments startIndex and count do not specify a valid section in the collection.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Int32 FindLastIndex(Int32 startIndex, Int32 count, Predicate<T> match)
		{
			return mList.FindLastIndex(startIndex, count, match);
		}

		#endregion

		#region ++ Public Interface (ForEach) ++

		/// <summary>
		/// Performs the specified action on each element of the collection.
		/// </summary>
		/// 
		/// <param name="action">
		/// The action delegate to perform on each element of the collection.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public void ForEach(Action<T> action)
		{
			mList.ForEach(action);
		}

		#endregion

		#region ++ Public Interface (GetRange) ++

		/// <summary>
		/// Creates a shallow copy of a range of elements in the source collection.
		/// </summary>
		/// 
		/// <param name="index">
		/// The zero-based index at which the range starts.</param>
		/// <param name="count">
		/// The number of elements in the range.</param>
		/// 
		/// <returns>
		/// A shallow copy of a range of elements in the source collection.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument index is less than 0.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument count is less than 0.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The arguments index and count do not specify a valid section in the collection.</exception>
		public List<T> GetRange(Int32 index, Int32 count)
		{
			return mList.GetRange(index, count);
		}

		#endregion

		#region ++ Public Interface (IndexOf, LastIndexOf) ++

		/// <summary>
		/// Searches for the specified object and returns the zero-based index of the first occurrence within 
		/// the entire collection.
		/// </summary>
		/// 
		/// <param name="item">
		/// The object to locate in the collection. The value can be null for reference types.</param>
		/// <param name="index">
		/// The zero-based starting index of the search. 0 (zero) is valid in an empty list.</param>
		/// 
		/// <returns>
		/// The zero-based index of the first occurrence of item within the range of elements in the collection
		/// that extends from index to the last element, if found; otherwise, -1.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument index is outside the range of valid indexes for the collection.</exception>
		public Int32 IndexOf(T item, Int32 index)
		{
			return mList.IndexOf(item, index);
		}

		/// <summary>
		/// Searches for the specified object and returns the zero-based index of the first occurrence within 
		/// the range of elements in the collection that starts at the specified index and contains the specified 
		/// number of elements.
		/// </summary>
		/// 
		/// <param name="item">
		/// The object to locate in the collection. The value can be null for reference types.</param>
		/// <param name="index">
		/// The zero-based starting index of the search. 0 (zero) is valid in an empty list.</param>
		/// <param name="count">
		/// The number of elements in the section to search.</param>
		/// 
		/// <returns>
		/// The zero-based index of the first occurrence of item within the range of elements in the collection 
		/// that starts at index and contains count number of elements, if found; otherwise, -1.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument index is outside the range of valid indexes for the collection.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument count is less than 0.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The arguments index and count do not specify a valid section in the collection.</exception>
		public Int32 IndexOf(T item, Int32 index, Int32 count)
		{
			return mList.IndexOf(item, index, count);
		}


		/// <summary>
		/// Searches for the specified object and returns the zero-based index of the last occurrence within 
		/// the entire collection.
		/// </summary>
		/// 
		/// <param name="item">
		/// The object to locate in the collection. The value can be null for reference types.</param>
		/// 
		/// <returns>
		/// The zero-based index of the last occurrence of item within the range of elements in the collection
		/// that extends from index to the last element, if found; otherwise, -1.</returns>
		public Int32 LastIndexOf(T item)
		{
			return mList.LastIndexOf(item);
		}

		/// <summary>
		/// Searches for the specified object and returns the zero-based index of the last occurrence within 
		/// the range of elements in the collection that extends from the first element to the specified index.
		/// </summary>
		/// 
		/// <param name="item">
		/// The object to locate in the collection. The value can be null for reference types.</param>
		/// <param name="index">
		/// The zero-based starting index of the backward search.</param>
		/// 
		/// <returns>
		/// The zero-based index of the last occurrence of item within the range of elements in the collection 
		/// that extends from the first element to index, if found; otherwise, -1.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument index is outside the range of valid indexes for the collection.</exception>
		public Int32 LastIndexOf(T item, Int32 index)
		{
			return mList.LastIndexOf(item, index);
		}

		/// <summary>
		/// Searches for the specified object and returns the zero-based index of the last occurrence within 
		/// the range of elements in the collection that contains the specified number of elements and ends at 
		/// the specified index.
		/// </summary>
		/// 
		/// <param name="item">
		/// The object to locate in the collection. The value can be null for reference types.</param>
		/// <param name="index">
		/// The zero-based starting index of the backward search.</param>
		/// <param name="count">
		/// The number of elements in the section to search.</param>
		/// 
		/// <returns>
		/// The zero-based index of the last occurrence of item within the range of elements in the collection 
		/// that contains count number of elements and ends at index, if found; otherwise, -1.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument index is outside the range of valid indexes for the collection.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument count is less than 0.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The arguments index and count do not specify a valid section in the collection.</exception>
		public Int32 LastIndexOf(T item, Int32 index, Int32 count)
		{
			return mList.LastIndexOf(item, index, count);
		}

		#endregion

		#region ++ Public Interface (InsertRange) ++

		/// <summary>
		/// Inserts the elements of a collection into this collection at the specified index.
		/// </summary>
		/// 
		/// <param name="index">
		/// The zero-based index at which the new elements should be inserted.</param>
		/// <param name="collection">
		/// The collection whose elements should be inserted into this collection. The collection itself cannot 
		/// be null, but it can contain elements that are null, if type T is a reference type.</param>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// A value was passed to a method that did not accept it as a valid argument, because the value must be 
		/// equal to or greater than the minimum value of 0 and equal to or less than 
		/// the maximum value of <see cref="P:System.Collections.ObjectModel.Collection{T}.Count"/>.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="InvalidOperationException">
		/// Reentrant attempts to change this collection are disallowed.</exception>
		public void InsertRange(Int32 index, IEnumerable<T> collection)
		{
			Verify.NotNull(collection, "collection");

			VerifyReentrancy();

			var count = this.Count;

			// insert items
			mList.InsertRange(index, collection);

			// return, if no items were inserted
			if (this.Count == count)
				return;

			// raise change events
			_RaisePropertyChanged(__propertyNameCount);
			_RaisePropertyChanged(__propertyNameIndexer);
			_RaisePropertyChanged(__propertyNameHasItems);

			_RaiseCollectionChangedAdded(() =>
				{
					var list = collection as IList;
					return list ?? collection.ToArray();
				},
				index);
		}

		#endregion

		#region ++ Public Interface (Move) ++

		/// <summary>
		/// Moves the item at the specified index to a new location in the collection.
		/// </summary>
		/// 
		/// <param name="oldIndex">
		/// The zero-based index specifying the location of the item to be moved.</param>
		/// <param name="newIndex">
		/// The zero-based index specifying the new location of the item.</param>
		public void Move(Int32 oldIndex, Int32 newIndex)
		{
			Verify.InRange(oldIndex, 0, this.Count - 1, "oldIndex");
			Verify.InRange(newIndex, 0, this.Count - 1, "newIndex");

			MoveItem(oldIndex, newIndex);
		}

		#endregion

		#region ++ Public Interface (RemoveAll, RemoveRange) ++

		/// <summary>
		/// Removes all the elements that match the conditions defined by the specified predicate.
		/// </summary>
		/// 
		/// <param name="match">
		/// The predicate delegate that defines the conditions of the elements to remove.</param>
		/// 
		/// <returns>
		/// The number of elements removed from the collection.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Int32 RemoveAll(Predicate<T> match)
		{
			Verify.NotNull(match, "match");

			VerifyReentrancy();

			// remove items
			var count = mList.RemoveAll(match);

			// return, if no items were removed
			if (count == 0)
				return count;

			// raise change events
			_RaisePropertyChanged(__propertyNameCount);
			_RaisePropertyChanged(__propertyNameIndexer);
			_RaisePropertyChanged(__propertyNameHasItems);
			_RaiseCollectionChangedReset();

			return count;
		}


		/// <summary>
		/// Removes a range of elements from the collection.
		/// </summary>
		/// 
		/// <param name="index">
		/// The zero-based starting index of the range of elements to remove.</param>
		/// <param name="count">
		/// The number of elements to remove.</param>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument index is less than 0.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The argument count is less than 0.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// The arguments index and count do not specify a valid section in the collection.</exception>
		public void RemoveRange(Int32 index, Int32 count)
		{
			Verify.InRange(index, 0, mList.Count, "index");
			Verify.InRange(count, 0, mList.Count, "count");
			Verify.InRange(index + count, 0, mList.Count, "index");

			VerifyReentrancy();

			// return, if nothing will be removed
			if (count == 0)
				return;

			// get items
			var items = new T[count];
			mList.CopyTo(index, items, 0, count);

			// remove items
			mList.RemoveRange(index, count);

			// raise change events
			_RaisePropertyChanged(__propertyNameCount);
			_RaisePropertyChanged(__propertyNameIndexer);
			_RaisePropertyChanged(__propertyNameHasItems);
			_RaiseCollectionChangedRemoved(() => items, index);
		}

		#endregion

		#region ++ Public Interface (Sort) ++

		/// <summary>
		/// Sorts the elements in the entire collection using the default comparer.
		/// </summary>
		public void Sort()
		{
			// sort collection
			mList.Sort();

			// raise change events
			_RaiseCollectionChangedReset();
		}

		/// <summary>
		/// Sorts the elements in the entire collection using the specified comparison.
		/// </summary>
		/// 
		/// <param name="comparison">
		/// The comparison to use when comparing elements.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public void Sort(Comparison<T> comparison)
		{
			// sort collection
			mList.Sort(comparison);

			// raise change events
			_RaiseCollectionChangedReset();
		}

		/// <summary>
		/// orts the elements in the entire collection using the specified comparer.
		/// </summary>
		/// 
		/// <param name="comparer">
		/// The comparer implementation to use when comparing elements, or null to use the default comparer.</param>
		public void Sort(IComparer<T> comparer)
		{
			// sort collection
			mList.Sort(comparer);

			// raise change events
			_RaiseCollectionChangedReset();
		}

		#endregion

		#region ++ Public Interface (ToArray) ++

		/// <summary>
		/// Copies the elements of the collection to a new array.
		/// </summary>
		/// 
		/// <returns>
		/// An array containing copies of the elements of the collection.</returns>
		public T[] ToArray()
		{
			return mList.ToArray();
		}

		#endregion

		#region ## Overridable Methods ##

		/// <summary>
		/// Inserts an element into the collection at the specified index.
		/// </summary>
		/// 
		/// <param name="index">
		/// The zero-based index at which item should be inserted.</param>
		/// <param name="item">
		/// The object to insert. The value can be null for reference types.</param>
		protected override void InsertItem(Int32 index, T item)
		{
			VerifyReentrancy();

			// insert
			base.InsertItem(index, item);

			// raise change events
			_RaisePropertyChanged(__propertyNameCount);
			_RaisePropertyChanged(__propertyNameIndexer);
			_RaisePropertyChanged(__propertyNameHasItems);
			_RaiseCollectionChangedAdded(item, index);
		}

		/// <summary>
		/// Removes all elements from the collection.
		/// </summary>
		protected override void ClearItems()
		{
			VerifyReentrancy();

			if (this.Count == 0)
				return;

			// clear
			base.ClearItems();

			// raise change events
			_RaisePropertyChanged(__propertyNameCount);
			_RaisePropertyChanged(__propertyNameIndexer);
			_RaisePropertyChanged(__propertyNameHasItems);
			_RaiseCollectionChangedReset();
		}

		/// <summary>
		/// Removes the element at the specified index of the collection.
		/// </summary>
		/// 
		/// <param name="index">
		/// The zero-based index of the element to remove.</param>
		protected override void RemoveItem(Int32 index)
		{
			VerifyReentrancy();

			// remove item
			var item = mList[index];
			base.RemoveItem(index);

			// raise change events
			_RaisePropertyChanged(__propertyNameCount);
			_RaisePropertyChanged(__propertyNameIndexer);
			_RaisePropertyChanged(__propertyNameHasItems);
			_RaiseCollectionChangedRemoved(item, index);
		}

		/// <summary>
		/// Replaces the element at the specified index.
		/// </summary>
		/// 
		/// <param name="index">
		/// The zero-based index of the element to replace.</param>
		/// <param name="item">
		/// The new value for the element at the specified index. The value can be null for reference types.</param>
		protected override void SetItem(Int32 index, T item)
		{
			VerifyReentrancy();

			// set item
			var oldItem = mList[index];
			base.SetItem(index, item);

			// raise change events
			_RaisePropertyChanged(__propertyNameIndexer);
			_RaiseCollectionChangedReplaced(item, oldItem, index);
		}

		/// <summary>
		/// Moves the item at the specified index to a new location in the collection.
		/// </summary>
		/// 
		/// <param name="oldIndex">
		/// The zero-based index specifying the location of the item to be moved.</param>
		/// <param name="newIndex">
		/// The zero-based index specifying the new location of the item.</param>
		protected virtual void MoveItem(Int32 oldIndex, Int32 newIndex)
		{
			VerifyReentrancy();

			// move item
			var item = mList[oldIndex];
			mList.RemoveAt(oldIndex);
			mList.Insert(newIndex, item);

			// raise change events
			_RaisePropertyChanged(__propertyNameIndexer);
			_RaiseCollectionChangedMoved(item, newIndex, oldIndex);
		}

		#endregion

		#region ## Protected Interface ##

		/// <summary>
		/// Disallows reentrant attempts to change this collection.
		/// </summary>
		protected void BlockReentrancy()
		{
			mBusyCount++;
		}

		/// <summary>
		/// Allows reentrant attempts to change this collection.
		/// </summary>
		protected void UnblockReentrancy()
		{
			mBusyCount--;
		}

		/// <summary>
		/// Checks for reentrant attempts to change this collection.
		/// </summary>
		protected void VerifyReentrancy()
		{
			if (mBusyCount > 0)
				throw new InvalidOperationException("Reentrant attempts to change this collection are disallowed.");
		}


		/// <summary>
		/// Verifies that this object contains a public instance member property with the specified name.
		/// </summary>
		/// 
		/// <param name="propertyName">
		/// The name of the property.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// An empty string was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// The type does not contain a public instance member property with specified name.</exception>
		protected void VerifyPropertyName(String propertyName)
		{
			Verify.NotEmpty(propertyName, "propertyName");

			if (mLazyPropertyNames.Value.Contains(propertyName))
				return;

			var message = String.Format(CultureInfo.InvariantCulture,
				ExceptionResources.TypeDoesNotContainPublicInstanceProperty,
				this.GetType().FullName,
				propertyName);

			throw new ArgumentException(message, "propertyName");
		}

		#endregion

		#region Implementation

		private void _RaisePropertyChanged(String propertyName)
		{
			if (mChangedEventsSuspended)
				return;


			var handler = mPropertyChangedEvent;

			if (handler == null)
				return;

			handler(this, new PropertyChangedEventArgs(propertyName));

			#region (hook: property changed)
			ViewModelHooks.RaisePropertyChanged(this, propertyName);
			#endregion
		}


		private void _RaiseCollectionChangedReset()
		{
			_RaiseCollectionChanged(() => new NotifyCollectionChangedEventArgs(
				NotifyCollectionChangedAction.Reset));
		}

		private void _RaiseCollectionChangedAdded(T item, Int32 index)
		{
			_RaiseCollectionChanged(() => new NotifyCollectionChangedEventArgs(
				NotifyCollectionChangedAction.Add,
				(Object)item,
				index));
		}

		private void _RaiseCollectionChangedAdded(Func<IList> itemsFunc, Int32 index)
		{
			_RaiseCollectionChanged(() => new NotifyCollectionChangedEventArgs(
				NotifyCollectionChangedAction.Add,
				(IList)itemsFunc(),
				index));
		}

		private void _RaiseCollectionChangedRemoved(T item, Int32 index)
		{
			_RaiseCollectionChanged(() => new NotifyCollectionChangedEventArgs(
				NotifyCollectionChangedAction.Remove,
				(Object)item,
				index));
		}

		private void _RaiseCollectionChangedRemoved(Func<IList> itemsFunc, Int32 index)
		{
			_RaiseCollectionChanged(() => new NotifyCollectionChangedEventArgs(
				NotifyCollectionChangedAction.Remove,
				(IList)itemsFunc(),
				index));
		}

		private void _RaiseCollectionChangedReplaced(T newItem, T oldItem, Int32 index)
		{
			_RaiseCollectionChanged(() => new NotifyCollectionChangedEventArgs(
				NotifyCollectionChangedAction.Replace,
				(Object)newItem,
				(Object)oldItem,
				index));
		}

		private void _RaiseCollectionChangedMoved(T item, Int32 newIndex, Int32 oldIndex)
		{
			_RaiseCollectionChanged(() => new NotifyCollectionChangedEventArgs(
				NotifyCollectionChangedAction.Move,
				(Object)item,
				newIndex,
				oldIndex));
		}

		private void _RaiseCollectionChanged(Func<NotifyCollectionChangedEventArgs> func)
		{
			if (mChangedEventsSuspended)
				return;


			var handler = mCollectionChangedEvent;

			if (handler == null)
				return;

			try
			{
				BlockReentrancy();

				var args = func();
				handler(this, args);

				#region (hook: collection changed)
				ViewModelHooks.RaiseCollectionChanged(this, args.Action, args.NewStartingIndex, args.OldStartingIndex);
				#endregion
			}
			finally
			{
				UnblockReentrancy();
			}
		}

		private HashSet<String> _ResolvePropertyNames()
		{
			var properties = this.GetType().GetProperties(
				BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);

			var results = new HashSet<String>();

			foreach (var property in properties)
				results.Add(property.Name);

			return results;
		}

		#endregion

	}
}
